package com.sqtracker.recommender.algorithm;

import com.sqtracker.recommender.dto.RecommendationRequest;
import com.sqtracker.recommender.dto.RecommendationResponse;
import com.sqtracker.recommender.model.TorrentMetadataExt;
import com.sqtracker.recommender.model.UserBehavior;
import com.sqtracker.recommender.repository.TorrentMetadataExtRepository;
import com.sqtracker.recommender.repository.UserBehaviorRepository;
import com.sqtracker.recommender.util.SimilarityUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 基于物品的协同过滤推荐算法实现类
 *
 * 该算法通过分析物品之间的相似性进行推荐。核心思想是：
 * "喜欢某个物品的用户，也会喜欢与该物品相似的其他物品"
 *
 * 算法工作原理：
 * 1. 构建物品相似度矩阵：基于用户行为数据，计算物品之间的相似度
 * 2. 对于目标用户，找出其历史交互的物品
 * 3. 基于相似度矩阵，找出与用户历史物品相似的其他物品
 * 4. 根据相似度和用户行为权重计算推荐分数
 * 5. 返回评分最高的N个物品作为推荐结果
 *
 * 主要优势：
 * - 推荐解释性强：可以明确说明为什么推荐某个物品
 * - 推荐稳定性好：物品特征相对稳定，不像用户兴趣变化频繁
 * - 适合物品数量相对稳定的场景
 *
 * @author sqtracker
 * @version 1.0
 */
@Component
public class ItemCFRecommender implements RecommendationAlgorithm {

    private static final Logger logger = LoggerFactory.getLogger(ItemCFRecommender.class);

    private final UserBehaviorRepository userBehaviorRepository;
    private final TorrentMetadataExtRepository torrentMetadataExtRepository;

    /**
     * 物品相似度矩阵
     *
     * 数据结构：Map<物品ID, Map<相似物品ID, 相似度值>>
     * 例如：{"torrent1" -> {"torrent2" -> 0.8, "torrent3" -> 0.6}}
     * 表示torrent1与torrent2的相似度为0.8，与torrent3的相似度为0.6
     *
     * 使用ConcurrentHashMap保证线程安全
     */
    private Map<String, Map<String, Double>> itemSimilarityMatrix = new ConcurrentHashMap<>();

    /**
     * 构造函数，通过依赖注入初始化仓库
     */
    @Autowired
    public ItemCFRecommender(UserBehaviorRepository userBehaviorRepository,
                             TorrentMetadataExtRepository torrentMetadataExtRepository) {
        this.userBehaviorRepository = userBehaviorRepository;
        this.torrentMetadataExtRepository = torrentMetadataExtRepository;
    }

    /**
     * Spring容器初始化后执行的方法
     * 在应用启动时预计算物品相似度矩阵，提高推荐响应速度
     */
    @PostConstruct
    public void initializeSimilarityMatrix() {
        logger.info("Initializing ItemCF similarity matrix at startup...");
        try {
            computeItemSimilarityMatrix();
            logger.info("ItemCF similarity matrix initialized. Size: {} items with similarities.", itemSimilarityMatrix.size());
        } catch (Exception e) {
            logger.error("Error during initial computation of item similarity matrix", e);
        }
    }

    /**
     * 物品协同过滤推荐主方法
     *
     * 算法流程：
     * 1. 获取用户的历史行为数据
     * 2. 对于用户交互过的每个物品，找出相似物品
     * 3. 根据行为权重和相似度计算推荐分数
     * 4. 排序并返回Top-N推荐结果
     *
     * @param request 推荐请求，包含用户ID和推荐数量
     * @return 推荐响应，包含推荐的物品列表
     */
    @Override
    public RecommendationResponse recommend(RecommendationRequest request) {
        String userId = request.getUserId();
        int count = request.getCount();
        logger.debug("ItemCF recommending for userId: {}, count: {}", userId, count);

        // 步骤1：获取用户的历史行为数据
        List<UserBehavior> userBehaviors = userBehaviorRepository.findByUserId(userId);

        if (userBehaviors.isEmpty()) {
            logger.info("No user behaviors found for userId: {}. ItemCF returning empty recommendations.", userId);
            return new RecommendationResponse();
        }
        logger.debug("Found {} behaviors for userId: {}", userBehaviors.size(), userId);

        // 步骤2：提取用户已交互的物品ID集合，避免重复推荐
        Set<String> interactedTorrentIds = userBehaviors.stream()
                .map(UserBehavior::getTorrentId)
                .filter(Objects::nonNull) // 过滤null值
                .collect(Collectors.toSet());

        // 步骤3：计算推荐分数
        Map<String, Double> scoreMap = new HashMap<>();

        for (UserBehavior behavior : userBehaviors) {
            String torrentId = behavior.getTorrentId();
            if (torrentId == null) {
                logger.warn("UserBehavior found with null torrentId for userId: {}. Skipping.", userId);
                continue;
            }

            // 根据行为类型获取权重（下载>点赞>评分>查看）
            double behaviorWeight = getBehaviorWeight(behavior.getType());

            // 获取与当前物品相似的物品列表
            Map<String, Double> similarTorrents = getSimilarItems(torrentId);
            if (similarTorrents.isEmpty() && itemSimilarityMatrix.containsKey(torrentId)) {
                logger.warn("TorrentId {} exists in similarity matrix but has no similar items listed for it.", torrentId);
            }

            // 遍历相似物品，计算推荐分数
            for (Map.Entry<String, Double> entry : similarTorrents.entrySet()) {
                String similarTorrentId = entry.getKey();
                if (similarTorrentId == null) {
                    logger.warn("Found a null similarTorrentId for torrentId: {}", torrentId);
                    continue;
                }

                // 跳过用户已交互过的物品
                if (interactedTorrentIds.contains(similarTorrentId)) {
                    continue;
                }

                double similarity = entry.getValue(); // 物品相似度
                double score = behaviorWeight * similarity; // 推荐分数 = 行为权重 × 相似度
                scoreMap.put(similarTorrentId, scoreMap.getOrDefault(similarTorrentId, 0.0) + score);
            }
        }
        logger.debug("Score map for userId {}: {} potential recommendations before sorting.", userId, scoreMap.size());

        // 步骤4：按分数排序并限制数量
        List<Map.Entry<String, Double>> sortedItems = scoreMap.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed()) // 按分数降序排列
                .limit(count)
                .collect(Collectors.toList());
        logger.info("ItemCF generated {} recommendations for userId: {} after sorting and limiting.", sortedItems.size(), userId);

        // 步骤5：构建并返回推荐响应
        RecommendationResponse response = new RecommendationResponse();
        response.setRecommendations(buildRecommendationItems(sortedItems));

        if (response.getRecommendations().isEmpty() && !userBehaviors.isEmpty()) {
            logger.warn("ItemCF for user {} resulted in empty recommendations despite having {} behaviors and {} candidates in scoreMap.", userId, userBehaviors.size(), scoreMap.size());
        }

        return response;
    }

    /**
     * 根据行为类型获取权重
     *
     * 不同的用户行为表示不同的兴趣强度：
     * - DOWNLOAD（下载）：权重最高，表示强烈兴趣
     * - LIKE（点赞）：权重较高
     * - RATE（评分）：权重中等
     * - VIEW（查看）：权重较低
     * - 其他：权重最低
     *
     * @param behaviorType 行为类型
     * @return 对应的权重值
     */
    private double getBehaviorWeight(String behaviorType) {
        switch (behaviorType) {
            case "DOWNLOAD":
                return 5.0;  // 下载行为权重最高
            case "LIKE":
                return 3.0;  // 点赞行为权重较高
            case "RATE":
                return 2.0;  // 评分行为权重中等
            case "VIEW":
                return 1.0;  // 查看行为权重较低
            default:
                return 0.5;  // 其他行为权重最低
        }
    }

    /**
     * 获取与指定物品相似的物品列表
     *
     * @param torrentId 物品ID
     * @return 相似物品映射，key为相似物品ID，value为相似度值
     */
    private Map<String, Double> getSimilarItems(String torrentId) {
        if (torrentId == null) {
            logger.warn("getSimilarItems called with null torrentId.");
            return Collections.emptyMap();
        }

        if (itemSimilarityMatrix.containsKey(torrentId)) {
            return itemSimilarityMatrix.get(torrentId);
        }

        logger.warn("No similarity data found in matrix for torrentId: {}. Consider running computeItemSimilarityMatrix.", torrentId);
        return Collections.emptyMap();
    }

    /**
     * 构建推荐条目列表
     *
     * 将排序后的推荐结果转换为标准的推荐响应格式
     *
     * @param sortedItems 排序后的推荐项目列表
     * @return 推荐条目列表，包含torrentId、分数、算法名称和推荐理由
     */
    private List<Map<String, Object>> buildRecommendationItems(List<Map.Entry<String, Double>> sortedItems) {
        List<Map<String, Object>> recommendations = new ArrayList<>();

        for (Map.Entry<String, Double> entry : sortedItems) {
            String torrentId = entry.getKey();
            if (torrentId == null) {
                logger.warn("Attempted to build recommendation item with null torrentId.");
                continue;
            }
            double score = entry.getValue();

            // 验证物品元数据是否存在
            Optional<TorrentMetadataExt> metadata = torrentMetadataExtRepository.findByTorrentId(torrentId);

            if (metadata.isPresent()) {
                Map<String, Object> item = new HashMap<>();
                item.put("torrentId", torrentId);
                item.put("score", score);
                item.put("algorithm", "item-cf");
                item.put("reason", "根据您的历史喜好推荐");
                recommendations.add(item);
            } else {
                logger.warn("Could not find metadata for recommended torrentId: {} by ItemCF. Item skipped.", torrentId);
            }
        }
        return recommendations;
    }

    /**
     * 计算物品相似度矩阵
     *
     * 该方法是ItemCF算法的核心，通过分析用户行为数据计算物品之间的相似度。
     * 使用Jaccard相似度计算方法：相似度 = |交集| / |并集|
     *
     * 算法步骤：
     * 1. 构建用户-物品矩阵和物品-用户矩阵
     * 2. 对于每对物品，计算它们的共同用户集合
     * 3. 使用Jaccard相似度公式计算相似度
     * 4. 只保存相似度大于阈值（0.01）的物品对
     *
     * 时间复杂度：O(n²)，其中n为物品数量
     */
    public void computeItemSimilarityMatrix() {
        logger.info("Starting computation of item similarity matrix...");

        // 获取所有用户行为数据
        List<UserBehavior> allBehaviors = userBehaviorRepository.findAll();
        if (allBehaviors.isEmpty()) {
            logger.warn("No user behaviors found in the system. Item similarity matrix will be empty.");
            this.itemSimilarityMatrix = new ConcurrentHashMap<>();
            return;
        }
        logger.debug("Total user behaviors fetched for similarity calculation: {}", allBehaviors.size());

        // 构建用户-物品矩阵和物品-用户矩阵
        Map<String, Set<String>> userItemMatrix = new HashMap<>();  // 用户ID -> 交互物品ID集合
        Map<String, Set<String>> itemUserMatrix = new HashMap<>();  // 物品ID -> 交互用户ID集合

        for (UserBehavior behavior : allBehaviors) {
            String userId = behavior.getUserId();
            String torrentId = behavior.getTorrentId();

            // 数据清洗：确保用户ID和物品ID都不为null
            if (torrentId == null) {
                logger.warn("UserBehavior found with null torrentId. Skipping for itemUserMatrix population.");
                continue;
            }
            if (userId == null) {
                logger.warn("UserBehavior found with null userId for torrentId: {}. Skipping for itemUserMatrix population.", torrentId);
                continue;
            }

            // 构建双向映射关系
            userItemMatrix.computeIfAbsent(userId, k -> new HashSet<>()).add(torrentId);
            itemUserMatrix.computeIfAbsent(torrentId, k -> new HashSet<>()).add(userId);
        }
        logger.debug("Built userItemMatrix ({} users) and itemUserMatrix ({} items).", userItemMatrix.size(), itemUserMatrix.size());

        // 计算物品相似度矩阵
        Map<String, Map<String, Double>> newSimilarityMatrix = new ConcurrentHashMap<>();
        Set<String> allTorrentIds = itemUserMatrix.keySet();
        List<String> torrentIdList = new ArrayList<>(allTorrentIds);

        int comparisonsMade = 0;
        // 遍历所有物品对（避免重复计算，只计算上三角矩阵）
        for (int i = 0; i < torrentIdList.size(); i++) {
            String torrentId1 = torrentIdList.get(i);
            if (torrentId1 == null) {
                logger.error("Unexpected null torrentId1 encountered in torrentIdList. Index: {}", i);
                continue;
            }

            Set<String> users1 = itemUserMatrix.getOrDefault(torrentId1, Collections.emptySet());
            if (users1.isEmpty()) {
                continue;  // 跳过没有用户交互的物品
            }

            for (int j = i + 1; j < torrentIdList.size(); j++) {
                String torrentId2 = torrentIdList.get(j);
                if (torrentId2 == null) {
                    logger.error("Unexpected null torrentId2 encountered in torrentIdList. Index: {}", j);
                    continue;
                }

                Set<String> users2 = itemUserMatrix.getOrDefault(torrentId2, Collections.emptySet());
                if (users2.isEmpty()) {
                    continue;  // 跳过没有用户交互的物品
                }

                // 计算Jaccard相似度：|交集| / |并集|
                double similarity = SimilarityUtil.calculateJaccardSimilarity(users1, users2);
                comparisonsMade++;

                // 只保存相似度大于阈值的物品对，节省存储空间
                if (similarity > 0.01) {
                    // 由于相似度是对称的，需要在矩阵中存储双向关系
                    newSimilarityMatrix.computeIfAbsent(torrentId1, k -> new ConcurrentHashMap<>()).put(torrentId2, similarity);
                    newSimilarityMatrix.computeIfAbsent(torrentId2, k -> new ConcurrentHashMap<>()).put(torrentId1, similarity);
                }
            }

            // 进度日志，每处理100个物品输出一次进度
            if ((i + 1) % 100 == 0) {
                logger.debug("Processed {} of {} items for similarity calculation.", i + 1, torrentIdList.size());
            }
        }

        logger.info("Item similarity computation finished. Comparisons made: {}. New matrix size: {} items with similarities.", comparisonsMade, newSimilarityMatrix.size());

        // 原子性地更新相似度矩阵
        this.itemSimilarityMatrix = newSimilarityMatrix;
    }
}